#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>

#define NUM 1024
#define SIZE 32
#define SEP " "

//获取用户输入的命令行字符串
char cmd_line[NUM];

//将获取的字符串一个个分割开来
char* g_argv[SIZE];

// 环境变量的buffer 目的是新增的环境变量不被下一次memset清空
char g_tmpenv[64];

//重定向宏
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

int redir_status = NONE_REDIR;

char* CheckRedir(char* start)
{
    assert(start);
    //                                       |                 |
    char* end = start + strlen(start) - 1; //ls -a -l > file.txt\0
    while (end >= start)
    {
        //ls -a -l > file.txt
        //ls -a -l >> file.txt
        if (*end == '>')
        {
            if (*(end - 1) == '>' && *(end - 2) == ' ')
            {
                redir_status = APPEND_REDIR;
                *(end - 2) = '\0';
                end += 2;
                break;
            }
            redir_status = OUTPUT_REDIR;
            *(end - 1) = '\0';
            end += 2;
            break;
        }
        else if (*end == '<')
        {
            //cat < file.txt
            redir_status = INPUT_REDIR;
            *(end - 1) = '\0';
            end += 2;
            break;
        }
        else 
            end--;
    }
    if (end >= start)
    {
        return end; //目标文件名
    }
    else
    {
        return NULL;
    }
}

// shell运行原理: 父进程解析命令 等待子进程执行命令
int main()
{
    // extern char** environ;//debug

     //命令行解释器是一个常驻内存的进程(直到用户退出)
    while (1)
    {
        //显示提示信息
        printf("[root@localhost myshell]# ");
        fflush(stdout);
        memset(cmd_line, '\0', sizeof cmd_line);
        ///获取用户输入的命令行字符串 "ls -a -l -i"
        if (fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
        {
            continue;
        }
        //"ls -a -l -i\n\0"==>//"ls -a -l -i\0\0" 删除换行符以更贴切xshell
        cmd_line[strlen(cmd_line) - 1] = '\0';
        //debug: printf("echo: %s\n", cmd_line);//是否成功获得命令行字符串

        //在命令行字符串解析成字符数组之前 分析是否有重定向符号
        char* sep = CheckRedir(cmd_line);

        //命令行字符串解析："ls -a -l " -> "ls" "-a" "-l"
        g_argv[0] = strtok(cmd_line, SEP); //第一次调用传入原始字符串

        //一些特殊扩展的选项
        int index = 1;
        if (strcmp(g_argv[0], "ls") == 0)
        {
            g_argv[index++] = "--color=auto";
        }
        if (strcmp(g_argv[0], "ll") == 0)
        {
            g_argv[0] = "ls";
            g_argv[index++] = "-l";
            g_argv[index++] = "--color=auto";
        }

        while (g_argv[index++] = strtok(NULL, SEP)); //后续解析原始字符串,传入NULL

        //debug: 查看是否传给解析命令行字符串
        //for(index = 0; g_argv[index]; index++)
        //    printf("g_argv[%d]: %s\n", index, g_argv[index]);

        //内置命令/内建命令: 父进程（shell）自己执行的命令 本质是shell中的一个函数调用
        if (strcmp(g_argv[0], "cd") == 0)
        {
            if (g_argv[1] != NULL)
                chdir(g_argv[1]);

            continue;
        }
        if (strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL)
        {
            strcpy(g_tmpenv, g_argv[1]);
            putenv(g_tmpenv);
            //debug1: 查看是否传给添加env
            //int ret = putenv(g_tmpenv);
            //if (ret == 0) 
            //    printf("%s export success\n", g_argv[1]);
            // 
            //debug2: 显示所有env来查看是否传给添加env
            //for(int i = 0; environ[i]; i++)
            //    printf("%d: %s\n", i, environ[i]);
            continue;
        }

        //子进程执行获取到的命令
        pid_t id = fork();
        if (id == 0)
        {
            //命令中发生重定向事件
            if (sep != NULL)
            {
                int fd = -1;

                switch (redir_status)
                {
                case INPUT_REDIR:
                    fd = open(sep, O_RDONLY);
                    dup2(fd, 0);
                    break;
                case OUTPUT_REDIR:
                    fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);
                    dup2(fd, 1);
                    break;
                case APPEND_REDIR:
                    fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);
                    dup2(fd, 1);
                    break;
                default:
                    printf("bug?\n");
                    break;
                }
            }

            //debug: 子进程被替换前是否拿到env
            //printf("child's TMPENV: %s\n", getenv("TMPENV"));
            //printf("child's PATH: %s\n", getenv("PATH"));

            //ls -a -l
            //execvpe(g_argv[0], g_argv, environ);//ok: 显示传env 但没必要
            execvp(g_argv[0], g_argv);//ok test.c成功获取env
            exit(1);
        }
        //father
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if (ret > 0) 
            printf("exit code: %d\n", WEXITSTATUS(status));
    }
}

